package de.zainodis.triplog.data;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import de.zainodis.triplog.util.DateTimeUtils;

/**
 * Utility class to request and parse weather data from OWM.
 * Created by fzarrai on 8/11/2014.
 */
public class JsonParser {

    private static final String TAG = "JsonParser";

    /**
     * Prepare the weather high/lows for presentation.
     */
    public String formatHighLows(double high, double low) {
        // For presentation, assume the user doesn't care about tenths of a degree.
        long roundedHigh = Math.round(high);
        long roundedLow = Math.round(low);

        String highLowStr = roundedHigh + "/" + roundedLow;
        return highLowStr;
    }

    /**
     * This requires a location entry to be already existing, otherwise no weather data will be saved.
     *
     * @param forecastJsonStr the json string received from the server.
     * @param location        location entry to which the weather entries will be attributed. If null, nothing will be done.
     * @return the number of weather entries that were added to the database; -1 if an error occurred and no weather data was entered.
     * @throws JSONException on error
     */
    public static void storeWeatherDataFromJson(Context context, String forecastJsonStr, LocationEntry location)
            throws JSONException {
        if (location == null || forecastJsonStr == null) {
            Log.w(TAG, "Either location of json forecast string are null, aborting.");
        }

        Log.i(TAG, forecastJsonStr);

        // These are the names of the JSON objects that need to be extracted.
        final String OWM_LIST = "list";
        final String OWM_CITY_NAME = "name";
        final String OWM_WEATHER = "weather";
        final String OWM_HUMIDITY = "humidity";
        final String OWM_WIND = "speed";
        final String OWM_TEMPERATURE = "temp";
        final String OWM_MAX = "max";
        final String OWM_MIN = "min";
        final String OWM_DATETIME = "dt";
        final String OWM_DESCRIPTION = "main";
        final String OWM_ICON = "icon";
        final String OWM_CITY = "city";
        final String OWM_COORDINATES = "coord";
        final String OWM_LAT = "lat";
        final String OWM_LON = "lon";
        final String OWM_WIND_DIRECTION = "deg";

        JSONObject forecastJson = new JSONObject(forecastJsonStr);
        JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);

        List<WeatherEntry> reports = new ArrayList<WeatherEntry>();

        for (int i = 0; i < weatherArray.length(); i++) {
            WeatherEntry report = new WeatherEntry();

            //Build the weather object
            JSONObject dayForecast = weatherArray.getJSONObject(i);
            long dateTime = dayForecast.getLong(OWM_DATETIME);
            String day = DateTimeUtils.toDbDateString(new Date(dateTime * 1000L));
            JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0);
            String description = weatherObject.getString(OWM_DESCRIPTION);
            JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE);
            float high = (float) temperatureObject.getDouble(OWM_MAX);
            float low = (float) temperatureObject.getDouble(OWM_MIN);

            float speed = (float) dayForecast.getDouble(OWM_WIND);
            int humidity = dayForecast.getInt(OWM_HUMIDITY);
            float windDirection = (float) dayForecast.getDouble(OWM_WIND_DIRECTION);

            String iconId = weatherObject.getString(OWM_ICON);
            //Strip icon id of n/d (night/day) letter at the end - the weather icon has a two-digit number
            iconId = iconId.substring(0, 2);

            report.setDate(day);
            report.setMax(Math.round(high));
            report.setMin(Math.round(low));
            report.setDescription(description);
            report.setWindDirection(windDirection);
            report.setWind(speed);
            report.setHumidity(String.format("%s %%", humidity));
            report.setIconId(Integer.valueOf(iconId));
            report.setFkLocationEntry(location.getId());

            reports.add(report);
        }

        //Persist weather reports to the database
        location.setWeatherEntries(reports);
        location.createOrUpdate(context);
    }

    /**
     * Generates a location and returns it. This creates a new location and does not load an existing location from the database!
     *
     * @param forecastJsonStr       the json string received from the server.
     * @param locationRequestString the location string that has been used to request the weather data.
     * @return a LocationEntry instance including the related WeatherEntries.
     * @throws JSONException on error
     */
    public static LocationEntry getWeatherDataFromJson(Context context, String forecastJsonStr, String locationRequestString)
            throws JSONException {
        //TODO duplicate of other method, merge!

        // These are the names of the JSON objects that need to be extracted.
        final String OWM_LIST = "list";
        final String OWM_CITY_NAME = "name";
        final String OWM_WEATHER = "weather";
        final String OWM_HUMIDITY = "humidity";
        final String OWM_WIND = "speed";
        final String OWM_TEMPERATURE = "temp";
        final String OWM_MAX = "max";
        final String OWM_MIN = "min";
        final String OWM_DATETIME = "dt";
        final String OWM_DESCRIPTION = "main";
        final String OWM_ICON = "icon";
        final String OWM_CITY = "city";
        final String OWM_COORDINATES = "coord";
        final String OWM_LAT = "lat";
        final String OWM_LON = "lon";
        final String OWM_WIND_DIRECTION = "deg";

        JSONObject forecastJson = new JSONObject(forecastJsonStr);
        JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);

        JSONObject city = forecastJson.getJSONObject(OWM_CITY);
        String cityName = city.getString(OWM_CITY_NAME);
        JSONObject coordinates = city.getJSONObject(OWM_COORDINATES);
        double latitude = coordinates.getDouble(OWM_LAT);
        double longitude = coordinates.getDouble(OWM_LON);

        //Build the location object and insert it into the database
        LocationEntry location = new LocationEntry();
        location.setCityName(cityName);
        location.setLatitude(latitude);
        location.setLongitude(longitude);
        location.setLocationSetting(locationRequestString);

        List<WeatherEntry> reports = new ArrayList<WeatherEntry>();

        for (int i = 0; i < weatherArray.length(); i++) {
            WeatherEntry report = new WeatherEntry();

            //Build the weather object
            JSONObject dayForecast = weatherArray.getJSONObject(i);
            long dateTime = dayForecast.getLong(OWM_DATETIME);
            String day = DateTimeUtils.toDbDateString(new Date(dateTime * 1000L));
            JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0);
            String description = weatherObject.getString(OWM_DESCRIPTION);
            JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE);
            float high = (float) temperatureObject.getDouble(OWM_MAX);
            float low = (float) temperatureObject.getDouble(OWM_MIN);

            float speed = (float) dayForecast.getDouble(OWM_WIND);
            int humidity = dayForecast.getInt(OWM_HUMIDITY);
            float windDirection = (float) dayForecast.getDouble(OWM_WIND_DIRECTION);

            String iconId = weatherObject.getString(OWM_ICON);
            //Strip icon id of n/d (night/day) letter at the end - the weather icon has a two-digit number
            iconId = iconId.substring(0, 2);


            report.setDate(day);
            report.setMax(Math.round(high));
            report.setMin(Math.round(low));
            report.setDescription(description);
            report.setWindDirection(windDirection);
            report.setWind(speed);
            report.setHumidity(String.format("%s %%", humidity));
            report.setIconId(Integer.valueOf(iconId));

            reports.add(report);
        }
        location.setWeatherEntries(reports);
        return location;
    }

    /**
     * Requests the weather data using the given credentials. Make sure to run this on a background thread!
     *
     * @param location     location for which to request the weather
     * @param unit         unit of choice.
     * @param numberOfDays number of days for which the weather should be requested.
     * @return the resulting JSON string.
     */
    public static String requestWeatherForecast(String location, String unit, String numberOfDays) {

        StringBuilder builder = new StringBuilder();
        BufferedReader reader = null;
        HttpURLConnection urlConnection = null;

        String format = "json";

        final String FORECAST_BASE = "http://api.openweathermap.org/data/2.5/forecast/daily?";
        final String QUERY_PARAM = "q";
        final String FORMAT_PARAM = "mode";
        final String UNITS_PARAM = "units";
        final String DAYS_PARAM = "cnt";
        try {
            Uri uri = Uri.parse(FORECAST_BASE).buildUpon()
                    .appendQueryParameter(QUERY_PARAM, location)
                    .appendQueryParameter(FORMAT_PARAM, format)
                    .appendQueryParameter(UNITS_PARAM, unit)
                    .appendQueryParameter(DAYS_PARAM, numberOfDays)
                    .build();
            URL url = new URL(uri.toString());
            Log.v(TAG, url.toString());
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.connect();
            InputStream input = urlConnection.getInputStream();
            reader = new BufferedReader(new InputStreamReader(input));
            String line;
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }


        } catch (Throwable e) {
            Log.e(TAG, "Failed to obtain weather forecast.", e);
            return null;
        } finally {
            //Close connection and cleanup
            if (urlConnection != null) {
                urlConnection.disconnect();
                urlConnection = null;
            }
            if (reader != null) {
                try {
                    reader.close();
                    reader = null;
                } catch (IOException e) {
                    Log.e(TAG, "Failed to close stream.", e);
                }
            }
        }


        return builder.toString();
    }

}
